This Lasso 9 tip discusses how custom tags are defined in Lasso 9 and what advantages this has over earlier versions of Lasso.
Lasso allows the user to define tags which can then be used identically to the built
Lasso 9 introduces the first major revision in how custom tags are defined since they were introduced. The goals in Lasso 9 are to make custom tags easier to define, to promote better programming practice by enforcing parameter types, to better support overloading, and to make it easier to deal with parameter defaults.
The [Define_Tag] ... [/Define_Tag] tags from Lasso 8.5 and earlier are supported in addition to the new syntax. This allows tags which were defined in earlier versions of Lasso to continue to work. However, we will be encouraging developers to use the new syntax for new sites.
A tag is defined using the "define" keyword. The keyword expects to see a tag "signature" followed by the association operator => and then a codeblock which defines the tag. Each part of the custom tag definition will be discussed in more detail below.
A trivial custom tag might be defined as follows.
define helloWorld() => {
return('Hello World!');
}
When we call this tag it returns the expected value.
helloWorld;
Hello World!
The "define" keyword is also used to define types, traits, and other values in Lasso 9. These will be discussed further in later tips.
The signature of the custom tag defines what parameters the tag requires or expects, the types and default values of those parameters, and the return type for the tag.
Note that an empty signature, as seen above, specifies that a tag must be called with no parameters. Calling helloWorld('') generates an error since there is no tag with that signature.
We can define a tag with a catch
define helloWorld(...) => {
return('Hello World!');
}
The signature can be used to specify required parameters for the tag. The parameters should be listed in order. When the tag is called each parameter will be automatically assigned to a variable of the same name. If the tag is called without the required number of parameters then an error will be generated.
define greeting(first, last) => {
return('Hello ' + #first + ' ' + #last);
}
The type of each parameter can be specified using ::type after each parameter name. By adding ::string to each parameter we can guarantee that we are passed only string parameters. This can prevent errors which happen when numbers or byte streams are passed to a tag expecting strings. If the tag is called with parameters of the wrong type then an error will be generated.
define greeting(first::string, last::string) => {
return('Hello ' + #first + ' ' + #last);
}
The special type of ::any can be used to explicitly specify that a parameter can be of any data type. Alternately, the type can simply be omitted.
The tag can be called with or without parameter names. The call greeting('John', 'Doe') will return the same value as greeting(
Note that the options for required parameters are directly analogous to using the
An optional parameter is specified the same as a required parameter, but is given a default value. The default value is specified using an equal sign after each parameter in the signature.
For example, the following code makes the last name parameter optional by assigning it a default value of ''. Now the tag can be called as greeting('John') or as greeting('John Doe').
define greeting(first, last::string='') => {
return('Hello ' + #first + ' ' + #last);
}
All of the required parameters of a tag must be specified before all of the optional parameters of the tag.
The treatment of optional parameters in Lasso 9 differs from how they were treated in earlier versions of Lasso. The ability to assign a default value to each optional value can significantly reduce code within the tag. All of the logic required to check whether a given parameter has been defined or not can be eliminated.
The following tag signature defines three optional parameters which all default to good values for the tag. The tag can be called simply as openConnection() to make a connection back to the local host.
define openConnection(
host::string='localhost',
port::integer=110,
timeout::integer=15) => {
...
}
In Lasso 8.5 I might define this tag as follows. The logic to set default values is not difficult, but it requires extra work and provides one more area where bugs might be introduced.
Define_Tag('openConnection',
-Required='host', -Type='string',
-Required='port', -Type='integer',
-Required='timeout', -Type='integer')
local('_host') = local_defined('host') ? #host | 'localhost';
local('_port') = local_defined('port') ? #port | 110;
local('_timeout') = local_defined('timeout') ? #timeout | 15;
...
/Define_Tag;
Rest of the Parameters
It is sometimes not possible to know the names of the parameters of a tag before it is called. The special #rest variable allows access to all the rest of the parameters of a tag after the required and named optional parameters have been accessed.
The #rest variable is created if you include an ellipsis ... as the last entry in the signature. The catch-all signature, which accepts any parameters passed to a tag is simply an ellipsis. Otherwise, the ellipsis can be used with any combination of required and optional parameters.
For example, the [Session_AddVar] tag requires the -Name of a session. The rest of the parameters name variables that should be tracked by the session. The ellipsis and #rest parameters are a natural fit.
[pre]
define session_addvar(name::string, ...) => {
// Check that the session is valid
!session_isvalid(#name) ? return
// Add the variables to the session
iterate(#rest);
session_vararray(#name)->insert(loop_value)
/iterate;
}
The [Params] tag can still be used to access all of the parameters passed to the tag. The advantage of using #rest is that only those parameters which did not match one of the required or optional parameters are included.
The return type for the tag can be specified after the signature. This can be helpful in making sure that your tags are returning proper values. The keyword ::any can be used to explicitly state that a tag returns several types of parameters. An error will be generated if a tag attempts to return a value other than the specified type.
define greeting(first::string, last::string)::string => {
return('Hello ' + #first + ' ' + #last);
}
The codeblock which defines the tag can be specified in several different ways. The normal codeblock requires a "return" keyword in order to return a value from the tag.
define greeting(first::string, last::string)::string => {
return('Hello ' + #first + ' ' + #last);
}
If an auto
define greeting(first::string, last::string)::string => {^
'Hello ' + #first + ' ' + #last;
^}
If the implementation of the tag consists of only a single statement then the statement can be specified directly without curly braces.
define add(first::any, second::any):any => #first + #second
A tag is overloaded when it is defined multiple times with the same signature. When a tag is called the parameters are examined and the tag with the closes signature is used. A tag which specifies explicit types for its parameters will be used before a tag which allows any type for the parameters.
For example, we have defined the tag [Add] which accepts parameters of any type and feeds them to the + symbol.
define add(first::any, second::any)::any => #first + #second
We might like to be able to add two arrays together, but we want different behavior than the + symbol is going to produce. We can define a new [Add] tag which takes two array parameters and returns an array parameter.
define add(first::array, second::array)::array => {
local(out) = array;
#out->insertfrom(#first);
#out->insertfrom(#second);
return(#out);
}
Now, when we call Add(1,2) the catch